<?php


namespace App\Http\Library;


class Lock
{
    /*缓存连接*/
    private static $conn;
    /*缓存建立连接的时间*/
    private static $connTime = 0;


    /**
     * 加锁
     *
     * @param string $key    加锁的key     请不要使用int类型的key,int类型的key已被Queue任务使用.
     * @param int    $wait   锁等待时间,秒
     * @param int    $expire 锁失效时间,秒
     *
     * @return bool false 没有拿到锁,true 获取到锁
     */
    public static function lock(string $key, int $wait = 3, int $expire = 10) :bool
    {
        if (empty($key) && $key !== '0') {
            return false;
        }
        /*已经等待的时间:毫秒*/
        $hasWait = 0;
        $key = self::prefix($key);
        $data = serialize([
            /*进程ID*/
            'pid'    => posix_getpid(),
            'ctime'  => date('Y-m-d H:i:s'),
            /*过期时间*/
            'expire' => time() + $expire,
        ]);
        /*计算过期毫秒数*/
        $waitMs = $wait * 1000;
        do {
            /*SET if Not eXists 命令在指定的 key 不存在时,为 key 设置指定的值*/
            $getLock = (bool) self::conn()->setNx($key, $data);
            /*没拿到锁*/
            if ($getLock === false) {
                /*等待超时*/
                if ($hasWait > $waitMs) {
                    return false;
                }
                /*等待100ms 即 0.1秒*/
                usleep(100000);
                $hasWait += 100;
                continue;
            }
            /*如果拿到锁,则设置锁过期时间,防止死锁*/
            self::conn()->expire($key, $expire);
            return true;
        } while (true);
    }


    /**
     * 释放锁
     *
     * @param string $key
     *
     * @return bool
     */
    public static function unlock(string $key) :bool
    {
        $key = self::prefix($key);
        $data = self::conn()->get($key);
        if (empty($data)) {
            return true;
        }
        $data = unserialize($data);
        /*如果锁属于当前进程,则删除锁*/
        if ($data['pid'] === posix_getpid()) {
            return (bool) self::conn()->del($key);
        }
        return false;
    }


    /**
     * 缓存key
     *
     * @param string $key
     *
     * @return string
     */
    private static function prefix(string $key)
    {
        return config('cache.prefix') . ":LOCK:{$key}";
    }


    /**
     * 获取缓存连接
     *
     * @return mixed
     */
    private static function conn()
    {
        /*redis timeout时间一般为十秒,这里设置9秒重连*/
        if ((time() - self::$connTime) > 9) {
            self::$connTime = time();
            /*缓存连接*/
            self::$conn = \Illuminate\Support\Facades\Cache::connection();
        }

        return self::$conn;
    }

}





/**
 * Lock.php
 *
 * 说明:
 *
 * 修改历史
 * ----------------------------------------
 * 2021/5/14 18:27:00   操作:创建
 **/
